package com.zeratul.common.satoken.utils;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.zeratul.common.core.context.DataContextHolde;
import com.zeratul.common.core.exception.ServiceException;
import com.zeratul.common.core.result.codes.ResultCodes;
import com.zeratul.common.core.utils.spring.SpringUtils;
import com.zeratul.common.json.utils.JsonUtils;
import com.zeratul.common.satoken.constants.SaTokenConstants;
import com.zeratul.common.satoken.domain.entity.Guest;
import com.zeratul.common.satoken.domain.entity.base.LoginUserAdapter;
import com.zeratul.common.satoken.enums.TokenModel;
import com.zeratul.common.satoken.enums.UserType;
import lombok.extern.slf4j.Slf4j;

/**
 * 登录用户的工具类
 */
@Slf4j
public class LoginHelper {

    /**
     * 登录方法
     * @param loginUser
     */
    public static void login(LoginUserAdapter loginUser){
        //创建SaToken登录参数对象
        SaLoginParameter saLoginParameter = new SaLoginParameter()
                //设备类型
                .setDeviceType(loginUser.getDeviceType())
                //设备ID
                .setDeviceId(loginUser.getDeviceId())
                //是否允许同一账号多地同时登录
                .setIsConcurrent(loginUser.getIsConcurrent());

        //判断当前的Token模式是否为Jwt模式
        if (isJwtMixin()) {
            //该模式下，用户信息直接加入Token中
            saLoginParameter.setExtra(SaTokenConstants.LOGIN_USER_KEY, JsonUtils.serialize(loginUser));
            saLoginParameter.setExtra(SaTokenConstants.LOGIN_USER_TYPE_KEY, loginUser.getClass().getName());
        }

        //设置Token过期时间
        if (ObjectUtil.isNotNull(loginUser.getTimeoutSec())) {
            saLoginParameter.setTimeout(loginUser.getTimeoutSec());
        }

        //进行SaToken登录
        StpUtil.login(loginUser.getLoginId(), saLoginParameter);

        //非无状态模式，用户信息则存储到Session中
        if (isRedis()) {
            //存储登录用户信息到Session中
            setSessionValue(SaTokenConstants.LOGIN_USER_KEY, loginUser);
        }
    }

    /**
     * 获取当前登录用户信息
     */
    @SuppressWarnings("unchecked")
    public static <T extends LoginUserAdapter> T getLoginUser() {
        //登录的用户信息
        LoginUserAdapter loginUser = null;
        //从请求上下文中获取用户信息
        loginUser = DataContextHolde.getReqData(SaTokenConstants.LOGIN_USER_KEY);
        //如果已经获取过用户信息，则直接返回
        if (ObjectUtil.isNotNull(loginUser)) {
            return (T) loginUser;
        }

        //获取当前登录用户信息
        try {
            //判断是否登录
            if (!isLogin()) {
                //如果未登录，则直接返回游客对象
                loginUser = new Guest();
            } else if (isJwtMixin()) {
                //Jwt模式下，用户信息直接存放在Token中，直接从Token中获取用户信息
                String loginUserJson = StpUtil.getExtra(SaTokenConstants.LOGIN_USER_KEY).toString();
                String loginUserTypeCls = StpUtil.getExtra(SaTokenConstants.LOGIN_USER_TYPE_KEY).toString();
                loginUser = (LoginUserAdapter) JsonUtils.deserialize(loginUserJson, Class.forName(loginUserTypeCls));
            } else {
                //非Jwt模式，用户信息则从Session中获取
                loginUser = getSessionValue(SaTokenConstants.LOGIN_USER_KEY);
            }

            //将用户信息放入上下文中
            DataContextHolde.setReqData(SaTokenConstants.LOGIN_USER_KEY, loginUser);
            return (T) loginUser;
        } catch (Exception e) {
            log.error("【获取登录信息】获取登录信息失败", e);
            throw new ServiceException(ResultCodes.AUTH_ERROR);
        }
    }

    /**
     * 获取当前登录用户类型
     */
    public static UserType getUserType() {
        LoginUserAdapter loginUserAdapter = getLoginUser();
        if (ObjectUtil.isNull(loginUserAdapter)) {
            return null;
        }
        //查询登录类型
        return loginUserAdapter.getUserType();
    }

    /**
     * 在会话中，根据Key获取Value
     */
    @SuppressWarnings("unchecked")
    public static <T> T getTokenSessionValue(String key) {
        SaSession tokenSession = StpUtil.getTokenSession();
        if (ObjectUtil.isNull(tokenSession)) {
            return null;
        }
        return (T) tokenSession.get(key);
    }

    /**
     * 在会话中，根据Key设置Value
     */
    public static void setTokenSessionValue(String key, Object value) {
        SaSession tokenSession = StpUtil.getTokenSession();
        if (ObjectUtil.isNull(tokenSession)) {
            return;
        }
        tokenSession.set(key, value);
    }

    /**
     * 获取当前用户中，根据Key获取Value
     */
    @SuppressWarnings("unchecked")
    public static <T> T getSessionValue(String key) {
        SaSession session = StpUtil.getSession();
        if (ObjectUtil.isNull(session)) {
            return null;
        }
        return (T) session.get(key);
    }

    /**
     * 获取当前用户中，根据Key设置Value
     */
    public static void setSessionValue(String key, Object value) {
        SaSession session = StpUtil.getSession();
        if (ObjectUtil.isNull(session)) {
            return;
        }
        session.set(key, value);
    }

    /**
     * 获取当前的Token
     */
    public static String getToken(){
        return StpUtil.getTokenValue();
    }

    /**
     * 检查当前是否已登录
     *
     * @return 结果
     */
    public static boolean isLogin() {
        try {
            return StpUtil.isLogin();
        } catch (Exception e) {
            //如果异常，则认为是未登录
            return false;
        }
    }

    /**
     * 检测当前是否登录，未登录则抛出异常
     */
    public static void checkLogin() {
        StpUtil.checkLogin();
    }

    /**
     * 获取登录的用户ID
     */
    public static String getUserId() {
        return Convert.toStr(getLoginUser().getUserId());
    }

    /**
     * 获取登录的用户ID - Long类型
     */
    public static Long getUserIdLong() {
        return Convert.toLong(getLoginUser().getUserId());
    }

    /**
     * 注销
     */
    public static void logout() {
        StpUtil.logout();
    }

    /**
     * 判断当前模式是否为Jwt模式
     */
    public static boolean isJwtMixin() {
        return TokenModel.getByValue(SpringUtils.getProperty(SaTokenConstants.TOKEN_MODEL_KEY)) == TokenModel.JWT_MIXIN;
    }

    /**
     * 判断当前模式是否为Redis模式
     */
    public static boolean isRedis() {
        return TokenModel.getByValue(SpringUtils.getProperty(SaTokenConstants.TOKEN_MODEL_KEY)) == TokenModel.REDIS;
    }
}
